/** @file ar_chardev.c
  *
  * @brief This file contains the char device function calls
  *
  *  Copyright (C) 2024, Artosyn Technologies. ALL RIGHTS RESERVED.
  *
  *  This Information is proprietary to Artosyn Technologies and MAY NOT
  *  be copied by any method or incorporated into another program without
  *  the express written consent of Artosyn. This Information or any portion
  *  thereof remains the property of Artosyn. The Information contained herein
  *  is believed to be accurate and Artosyn assumes no responsibility or
  *  liability for its use in any way and conveys no license or title under
  *  any patent or copyright and makes no representation or warranty that this
  *  Information is free from patent or copyright infringement.
  *
  */

#include <linux/path.h>
#include <linux/namei.h>
#include <linux/mount.h>
#include <linux/skbuff.h>
#include <linux/poll.h>
#include <linux/netdevice.h>
#include <linux/kernel.h>
#include <linux/version.h>

#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 11, 0)
#include <linux/sched/signal.h>
#endif
#include "utils/utils.h"
#include "control/ar_chardev.h"

#undef CONFIG_COMPAT

static LIST_HEAD(char_dev_list);

static DEFINE_SPINLOCK(char_dev_list_lock);

static int char_dev_major = AR_CHARDEV_MAJOR_NUM;

/**
 *  @brief  Gets char device structure
 *
 *  @param dev      A pointer to ar_cdev
 *
 *  @return         kobject structure
 */
static struct kobject *
ar_chardev_get(struct ar_cdev *dev)
{
    struct kobject *kobj;

    kobj = kobject_get(&dev->kobj);
    return kobj;
}

/**
 *  @brief  Prints char device structure
 *
 *  @param dev      A pointer to ar_cdev
 *
 *  @return         N/A
 */
static void
ar_chardev_put(struct ar_cdev *dev)
{
    if (dev) {
        AR_DBG(DM_GENERIC, DL_INF, "dev put kobj\n");
        kobject_put(&dev->kobj);
    }
}

/**
 *  @brief  Prints char device structure
 *
 *  @param dev_type     Refers to driver_chrdev_type
 *
 *  @return             N/A
 */
static struct m_dev*
char_dev_get_mdev(int dev_type)
{
    int ret = 0;
    struct ar_cdev *dev = NULL;
    struct ar_cdev *cdev = NULL;
    struct list_head *p = NULL;

    list_for_each(p, &char_dev_list) {
        cdev = list_entry(p, struct ar_cdev, list);
        if (cdev->dev_type == dev_type) {
            dev = cdev;
            break;
        }
    }

    if (!dev) {
        return NULL;
    }

    if (!ar_chardev_get(dev)) {
        return NULL;
    }

    return dev->m_dev;
}

#if defined(CDEV_SET_PERMISSION)
/**
 *  @brief Changes permissions of the dev
 *
 *  @param name pointer to character
 *  @param mode     mode_t type data
 *  @return         0--success otherwise failure
 */
int
ar_chardev_chmod(char *name, mode_t mode)
{
    struct path path;
    struct inode *inode;
    struct iattr newattrs;
    int ret;
    int retrycount = 0;

    AR_DBG(DM_CDEV, DL_INF, AR_FN_ENTRY_STR);
    do {
        os_sched_timeout(30);
        ret = kern_path(name, LOOKUP_FOLLOW, &path);
        if (++retrycount >= 10) {
            AR_DBG(DM_GENERIC, DL_ERR,
                   "char_dev_chmod(): fail to get kern_path. name %s ret %d\n", name, ret);
            AR_DBG(DM_CDEV, DL_INF, AR_FN_LEAVE_STR);
            return -EFAULT;
        }
    } while (ret);
    inode = path.dentry->d_inode;

#if LINUX_VERSION_CODE < KERNEL_VERSION(4,7,0)
    mutex_lock(&inode->i_mutex);
#else
    inode_lock(inode);
#endif
    ret = mnt_want_write(path.mnt);
    if (ret)
        goto out_unlock;
    newattrs.ia_mode = (mode & S_IALLUGO) | (inode->i_mode & ~S_IALLUGO);
    newattrs.ia_valid = ATTR_MODE | ATTR_CTIME;
    if (inode->i_op->setattr)
#if LINUX_VERSION_CODE >= KERNEL_VERSION(5,12,0)
        ret = inode->i_op->setattr(&init_user_ns, path.dentry, &newattrs);
#else
        ret = inode->i_op->setattr(path.dentry, &newattrs);
#endif
    else
#if LINUX_VERSION_CODE >= KERNEL_VERSION(5,12,0)
        ret = simple_setattr(&init_user_ns, path.dentry, &newattrs);
#else
        ret = simple_setattr(path.dentry, &newattrs);
#endif

#if LINUX_VERSION_CODE < KERNEL_VERSION(4,7,0)
    mutex_unlock(&inode->i_mutex);
#else
    inode_unlock(inode);
#endif
    mnt_drop_write(path.mnt);

    path_put(&path);
    AR_DBG(DM_CDEV, DL_INF, AR_FN_LEAVE_STR);
    return ret;
out_unlock:
#if LINUX_VERSION_CODE < KERNEL_VERSION(4,7,0)
    mutex_unlock(&inode->i_mutex);
#else
    inode_unlock(inode);
#endif
    mnt_drop_write(path.mnt);
    path_put(&path);
    return ret;
}

/**
 *  @brief Changes ownership of the dev
 *
 *  @param name pointer to character
 *  @param user     uid_t type data
 *  @param group    gid_t type data
 *  @return         0--success otherwise failure
 */
static int
char_dev_chown(char *name, uid_t user, gid_t group)
{
    struct path path;
    struct inode *inode = NULL;
    struct iattr newattrs;
    int ret = 0;
    int retrycount = 0;

    AR_DBG(DM_CDEV, DL_INF, AR_FN_ENTRY_STR);
    do {
        os_sched_timeout(30);
        ret = kern_path(name, LOOKUP_FOLLOW, &path);
        if (++retrycount >= 10) {
            AR_DBG(DM_GENERIC, DL_ERR,
                   "char_dev_chown(): fail to get kern_path\n");
            AR_DBG(DM_CDEV, DL_INF, AR_FN_LEAVE_STR);
            return -EFAULT;
        }
    } while (ret);
    inode = path.dentry->d_inode;
#if LINUX_VERSION_CODE < KERNEL_VERSION(4,7,0)
    mutex_lock(&inode->i_mutex);
#else
    inode_lock(inode);
#endif
    ret = mnt_want_write(path.mnt);
    if (ret)
        goto out_unlock;
    newattrs.ia_valid = ATTR_CTIME;
    if (user != (uid_t) (-1)) {
        newattrs.ia_valid |= ATTR_UID;
        newattrs.ia_uid = KUIDT_INIT(user);
    }
    if (group != (gid_t) (-1)) {
        newattrs.ia_valid |= ATTR_GID;
        newattrs.ia_gid = KGIDT_INIT(group);
    }
    if (!S_ISDIR(inode->i_mode))
        newattrs.ia_valid |=
            ATTR_KILL_SUID | ATTR_KILL_SGID | ATTR_KILL_PRIV;
    if (inode->i_op->setattr)
#if LINUX_VERSION_CODE >= KERNEL_VERSION(5,12,0)
        ret = inode->i_op->setattr(&init_user_ns, path.dentry, &newattrs);
#else
        ret = inode->i_op->setattr(path.dentry, &newattrs);
#endif
    else
#if LINUX_VERSION_CODE >= KERNEL_VERSION(5,12,0)
        ret = simple_setattr(&init_user_ns, path.dentry, &newattrs);
#else
        ret = simple_setattr(path.dentry, &newattrs);
#endif

#if LINUX_VERSION_CODE < KERNEL_VERSION(4,7,0)
    mutex_unlock(&inode->i_mutex);
#else
    inode_unlock(inode);
#endif
    mnt_drop_write(path.mnt);

    path_put(&path);
    AR_DBG(DM_CDEV, DL_INF, AR_FN_LEAVE_STR);
    return ret;
out_unlock:
#if LINUX_VERSION_CODE < KERNEL_VERSION(4,7,0)
    mutex_unlock(&inode->i_mutex);
#else
    inode_unlock(inode);
#endif
    mnt_drop_write(path.mnt);
    path_put(&path);
    return ret;
}
#endif

/**
 *  @brief write handler for char dev
 *
 *  @param filp pointer to structure file
 *  @param buf      pointer to char buffer
 *  @param count    size of receive buffer
 *  @param f_pos    pointer to loff_t type data
 *  @return         number of bytes written
 */
static ssize_t
ar_chardev_write(struct file * filp, const char __user *buf, size_t count, loff_t * f_pos)
{
    int nwrite = 0;
    unsigned char *buffer;
    struct ar_cdev *dev = (struct ar_cdev *)filp->private_data;
    struct m_dev *m_dev = NULL;

    if (!dev) {
        AR_DBG(DM_CDEV, DL_INF, AR_FN_LEAVE_STR);
        return -ENXIO;
    }

    m_dev = dev->m_dev;

    if (!m_dev) {
        AR_DBG(DM_CDEV, DL_INF, AR_FN_LEAVE_STR);
        return -ENXIO;
    }

    if(mdev_req_lock(m_dev)) {
        return -ERESTARTSYS;
    }

    if (!test_bit(CHAR_DEV_UP, &m_dev->flags)) {
        mdev_req_unlock(m_dev);
        AR_DBG(DM_CDEV, DL_INF, AR_FN_LEAVE_STR);
        return -EBUSY;
    }
    nwrite = count;

    buffer = kmalloc(count, GFP_KERNEL);
    if (!buffer) {
        AR_DBG(DM_GENERIC, DL_ERR, "ar_chardev_write(): fail to alloc buffer\n");
        mdev_req_unlock(m_dev);
        AR_DBG(DM_CDEV, DL_INF, AR_FN_LEAVE_STR);
        return -ENOMEM;
    }

    if (copy_from_user((void *)buffer, buf, count)) {
        AR_DBG(DM_GENERIC, DL_ERR, "ar_chardev_write(): cp_from_user failed\n");
        kfree(buffer);
        nwrite = -EFAULT;
        goto exit;
    }

    /* Send buffer to mdev */
    if (m_dev->send(m_dev, buffer, nwrite)) {
        AR_DBG(DM_GENERIC, DL_VRB, "Write: Fail\n");
        nwrite = 0;
    }

    /* Send failed */
    kfree(buffer);
exit:
    mdev_req_unlock(m_dev);

    return nwrite;
}

/**
 *  @brief read handler for BT char dev
 *
 *  @param filp pointer to structure file
 *  @param buf      pointer to char buffer
 *  @param count    size of receive buffer
 *  @param f_pos    pointer to loff_t type data
 *  @return         number of bytes read
 */
static ssize_t
ar_chardev_read(struct file * filp, char __user *buf, size_t count, loff_t * f_pos)
{
    struct ar_cdev *dev = (struct ar_cdev *)filp->private_data;
    struct m_dev *m_dev = NULL;
    DEFINE_WAIT(wait);
    ssize_t ret = 0;
    unsigned long flags;
    struct sk_buff *skb = NULL;

    if (!dev) {
        AR_DBG(DM_CDEV, DL_INF, AR_FN_LEAVE_STR);
        return -ENXIO;
    }

    m_dev = dev->m_dev;

    if (!m_dev) {
        AR_DBG(DM_CDEV, DL_INF, AR_FN_LEAVE_STR);
        return -ENXIO;
    }

    if (mdev_mutex_lock(m_dev)) {
        return -ERESTARTSYS;
    }

    mdev_spin_lock(m_dev, flags);
    while (1) {
        prepare_to_wait(&m_dev->req_wait_q, &wait, TASK_INTERRUPTIBLE);

        if (!test_bit(CHAR_DEV_UP, &m_dev->flags)) {
            ret = -EBUSY;
            break;
        }

        skb = skb_dequeue(&m_dev->rx_q);
        if (skb) {
            break;
        }

        if (filp->f_flags & O_NONBLOCK) {
            ret = -EAGAIN;
            break;
        }

        if (signal_pending(current)) {
            ret = -EINTR;
            break;
        }

        mdev_spin_unlock(m_dev, flags);
        schedule();
        mdev_spin_lock(m_dev, flags);
    }
    finish_wait(&m_dev->req_wait_q, &wait);
    mdev_spin_unlock(m_dev, flags);
    mdev_mutex_unlock(m_dev);

    if (!skb)
        goto out;

    if (skb->len > count) {
        /* user data length is smaller than the skb length */
        if (copy_to_user(buf, skb->data, count)) {
            ret = -EFAULT;
            goto outf;
        }
        skb_pull(skb, count);
        skb_queue_head(&m_dev->rx_q, skb);
        ret = count;
        goto out;
    } else {
        dump_bytes(DL_VRB, "DAEMON RX", skb->data, skb->len);
        if (copy_to_user(buf, skb->data, skb->len)) {
            ret = -EFAULT;
            goto outf;
        }
        ret = skb->len;
    }
outf:
    dev_kfree_skb_any(skb);
out:
    return ret;
}

/**
 *  @brief ioctl common handler for char dev
 *
 *  @param filp pointer to structure file
 *  @param cmd      contains the IOCTL
 *  @param arg      contains the arguement
 *  @return         0--success otherwise failure
 */
#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 36))
static long
char_ioctl(struct file* filp,
                u32 cmd, unsigned long arg)
#else
static int
char_ioctl(struct inode* inode, struct file* file,
                u32 cmd, unsigned long arg)
#endif
{
    int ret = 0;
#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 36))
    struct ar_cdev *dev = (struct ar_cdev *)filp->private_data;
#else
    struct ar_cdev *dev = (struct ar_cdev *)inode->i_private;
#endif
    struct m_dev *m_dev = NULL;

    AR_DBG(DM_CDEV, DL_INF, AR_FN_ENTRY_STR);
    if (!dev || !dev->m_dev) {
        AR_DBG(DM_CDEV, DL_INF, AR_FN_LEAVE_STR);
        return -ENXIO;
    }
    m_dev = dev->m_dev;
    AR_DBG(DM_GENERIC, DL_INF, "IOCTL: cmd=0x%x\n", cmd);

    if(mdev_mutex_lock(m_dev)) {
        return -ERESTARTSYS;
    }

    switch (cmd) {
        case AR_CHARDEV_IOCTL_NET_RELEASE:
            ret = m_dev->close(m_dev);
            break;
        default:
            ret = m_dev->ioctl(m_dev, cmd, arg);
            break;
    }

    mdev_mutex_unlock(m_dev);

    AR_DBG(DM_CDEV, DL_INF, AR_FN_LEAVE_STR);
    return ret;
}

/**
 *  @brief ioctl handler for char dev
 *
 *  @param filp     pointer to structure file
 *  @param inode    pointer to structure inode
 *  @param cmd      contains the IOCTL
 *  @param arg      contains the arguement
 *  @return         0--success otherwise failure
 */
#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 36))
static long
ar_chardev_ioctl(struct file* filp,
                unsigned int cmd, unsigned long arg)
{
    return char_ioctl(filp, cmd, arg);
}
#else
static int
ar_chardev_ioctl(struct inode* inode, struct file* file,
                u32 cmd, unsigned long arg)
{
    return char_ioctl(inode, cmd, arg);
}
#endif

/**
 *  @brief open handler for char dev
 *
 *  @param inode    pointer to structure inode
 *  @param filp pointer to structure file
 *  @return         0--success otherwise failure
 */
static int
ar_chardev_open(struct inode *inode, struct file *filp)
{
    int ret = 0;
    struct ar_cdev *dev = NULL;
    struct m_dev *m_dev = NULL;
    struct ar_cdev *cdev = NULL;
    struct list_head *p = NULL;
    unsigned long flags;
    AR_DBG(DM_CDEV, DL_INF, AR_FN_ENTRY_STR);

    list_for_each(p, &char_dev_list) {
        cdev = list_entry(p, struct ar_cdev, list);
        if (char_dev_major == MAJOR(inode->i_cdev->dev) &&
            cdev->minor == MINOR(inode->i_cdev->dev)) {
            dev = cdev;
            break;
        }
    }
    if (!dev) {
        AR_DBG(DM_USB, DL_ERR, "cannot find dev from inode\n");
        AR_DBG(DM_CDEV, DL_INF, AR_FN_LEAVE_STR);
        return -ENXIO;
    }
    if (!ar_chardev_get(dev)) {
        AR_DBG(DM_CDEV, DL_INF, AR_FN_LEAVE_STR);
        return -ENXIO;
    }
    filp->private_data = dev;    /* for other methods */
    m_dev = dev->m_dev;

    mdev_spin_lock(m_dev, flags);

    if (test_bit(CHAR_DEV_UP, &m_dev->flags)) {
        atomic_inc(&m_dev->extra_cnt);
        goto done;
    }
    if (m_dev->open(m_dev)) {
        ret = -EIO;
        goto done;
    }
    set_bit(CHAR_DEV_UP, &m_dev->flags);
done:
    mdev_spin_unlock(m_dev, flags);
    if (ret) {
        ar_chardev_put(dev);
    }
    AR_DBG(DM_CDEV, DL_INF, AR_FN_LEAVE_STR);
    return ret;
}

/**
 *  @brief release handler for char dev
 *
 *  @param inode    pointer to structure inode
 *  @param filp pointer to structure file
 *  @return         0--success otherwise failure
 */
static int
ar_chardev_release(struct inode *inode, struct file *filp)
{
    int ret = 0;
    struct ar_cdev *dev = (struct ar_cdev *)filp->private_data;
    struct m_dev *m_dev = NULL;
    unsigned long flags;
    AR_DBG(DM_CDEV, DL_INF, AR_FN_ENTRY_STR);
    if (!dev) {
        AR_DBG(DM_CDEV, DL_INF, AR_FN_LEAVE_STR);
        return -ENXIO;
    }
    m_dev = dev->m_dev;
    if (m_dev && (atomic_dec_if_positive(&m_dev->extra_cnt) >= 0)) {
        AR_DBG(DM_CDEV, DL_INF, AR_FN_LEAVE_STR);
        return ret;
    }

    if (m_dev) {
        ret = dev->m_dev->close(dev->m_dev);
    }

    filp->private_data = NULL;
    ar_chardev_put(dev);
    AR_DBG(DM_CDEV, DL_INF, AR_FN_LEAVE_STR);
    return ret;
}

/* File ops for the Char driver */
static const struct file_operations ar_chardev_fops = {
    .owner = THIS_MODULE,
    .read = ar_chardev_read,
    .write = ar_chardev_write,
#ifdef CONFIG_COMPAT
    .compat_ioctl = ar_chardev_ioctl,
    .unlocked_ioctl = ar_chardev_ioctl,
#else
#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 36))
    .unlocked_ioctl = ar_chardev_ioctl,
#else
    .ioctl = ar_chardev_ioctl,
#endif
#endif
    .compat_ioctl = ar_chardev_ioctl,
    .open = ar_chardev_open,
    .release = ar_chardev_release,
};

/**
 *  @brief This function creates the char dev
 *
 *  @param dev          A pointer to structure ar_cdev
 *  @param char_class   A pointer to class struct
 *  @param mod_name     A pointer to char
 *  @param dev_name     A pointer to char
 *  @return             0--success otherwise failure
 */
int
ar_register_char_dev(struct ar_cdev *dev, struct class *char_class,
          char *mod_name, char *dev_name)
{
    int ret = 0;
    dev_t dev_num;
    unsigned long flags;
    AR_DBG(DM_CDEV, DL_INF, AR_FN_ENTRY_STR);
    /* create the chrdev region */
    if (char_dev_major) {
        dev_num = MKDEV(char_dev_major, dev->minor);
        ret = register_chrdev_region(dev_num, 1, mod_name);
    } else {
        AR_DBG(DM_GENERIC, DL_MSG, "chardev: no major # yet\n");
        ret = alloc_chrdev_region(&dev_num, dev->minor, 1,
                      mod_name);
    }

    if (ret) {
        AR_DBG(DM_GENERIC, DL_ERR, "chardev: create chrdev_region failed. minor %d mod_name %s\n", dev->minor, mod_name);
        AR_DBG(DM_CDEV, DL_INF, AR_FN_LEAVE_STR);
        return ret;
    }
    if (!char_dev_major) {
        /* Store the allocated dev major # */
        char_dev_major = MAJOR(dev_num);
    }
    dev->cdev = cdev_alloc();
    if (!dev->cdev) {
        AR_DBG(DM_GENERIC, DL_ERR, "chardev: cdev_alloc failed. minor %d mod_name %s\n", dev->minor, mod_name);
        ret = -EFAULT;
        return ret;
    }

    cdev_init(dev->cdev, &ar_chardev_fops);
    dev->cdev->owner = ar_chardev_fops.owner;

    dev_num = MKDEV(char_dev_major, dev->minor);

    if (cdev_add(dev->cdev, dev_num, 1)) {
        AR_DBG(DM_GENERIC, DL_ERR, "chardev: cdev_add failed\n");
        ret = -EFAULT;
        goto free_cdev_region;
    }

    AR_DBG(DM_GENERIC, DL_MSG, "register char mod_name %s dev_name %s\n", mod_name, dev_name);

    device_create(char_class, NULL,
                dev_num, dev, "%s", dev_name);

    /** modify later */
    spin_lock_irqsave(&char_dev_list_lock, flags);
    list_add_tail(&dev->list, &char_dev_list);
    spin_unlock_irqrestore(&char_dev_list_lock, flags);

    AR_DBG(DM_CDEV, DL_INF, AR_FN_LEAVE_STR);
    return ret;
free_cdev_region:
    unregister_chrdev_region(MKDEV(char_dev_major, dev->minor), 1);
    AR_DBG(DM_CDEV, DL_INF, AR_FN_LEAVE_STR);
    return ret;
}

/**
 *  @brief This function deletes the char dev
 *
 *  @param dev          A pointer to structure ar_cdev
 *  @param char_class   A pointer to class struct
 *  @param dev_name     A pointer to char
 *  @return             0--success otherwise failure
 */
int
ar_unregister_char_dev(struct ar_cdev *dev, struct class *char_class,
            char *dev_name)
{
    AR_DBG(DM_CDEV, DL_INF, AR_FN_ENTRY_STR);

    if (dev && char_class) {
        AR_DBG(DM_GENERIC, DL_MSG, "unregister char dev major=%d minor=%d dev_name=%s\n",
                        char_dev_major, dev->minor, dev_name);
        device_destroy(char_class, dev->cdev->dev);
        cdev_del(dev->cdev);
        unregister_chrdev_region(MKDEV(char_dev_major, dev->minor), 1);
    }
    else {
        AR_DBG(DM_GENERIC, DL_ERR, "unregister char failed dev %p char_class %p\n", dev, char_class);
    }

    AR_DBG(DM_CDEV, DL_INF, AR_FN_LEAVE_STR);
    return 0;
}

/**
 *  @brief This function cleans module
 *
 *  @param m_dev    A pointer to m_dev struct
 *  @param char_class   A pointer to class struct
 *  @return         N/A
 */
void
ar_chardev_cleanup_one(struct m_dev *m_dev, struct class *char_class)
{
    unsigned long flags;
    struct list_head *p = NULL;
    struct ar_cdev *dev = NULL;
    AR_DBG(DM_CDEV, DL_INF, AR_FN_ENTRY_STR);

    spin_lock_irqsave(&char_dev_list_lock, flags);
    list_for_each(p, &char_dev_list) {
        dev = list_entry(p, struct ar_cdev, list);
        if (dev->minor == m_dev->index) {
            list_del(p);
            spin_unlock_irqrestore(&char_dev_list_lock, flags);
            dev->m_dev = NULL;
            ar_unregister_char_dev(dev, char_class, m_dev->name);
            kobject_put(&dev->kobj);
            spin_lock_irqsave(&char_dev_list_lock, flags);
            break;
        }
    }
    spin_unlock_irqrestore(&char_dev_list_lock, flags);
    AR_DBG(DM_CDEV, DL_INF, AR_FN_LEAVE_STR);
}

/**
 * @brief  Dynamic release of char dev
 *
 * @param kobj          A pointer to kobject structure
 *
 * @return                N/A
 */
static void
ar_chardev_release_dynamic(struct kobject *kobj)
{
    struct ar_cdev *cdev = container_of(kobj, struct ar_cdev, kobj);
    AR_DBG(DM_CDEV, DL_INF, AR_FN_ENTRY_STR);
    AR_DBG(DM_NET_DEV, DL_INF, "free ar_cdev\n");
    kfree(cdev);
    AR_DBG(DM_CDEV, DL_INF, AR_FN_LEAVE_STR);
}

static struct kobj_type ktype_char_dev_dynamic = {
    .release = ar_chardev_release_dynamic,
};

/**
 * @brief  Allocation of char dev
 *
 * @param               N/A
 *
 * @return              ar_cdev
 */
struct ar_cdev *
ar_alloc_char_dev(void)
{
    struct ar_cdev *cdev;
    AR_DBG(DM_CDEV, DL_INF, AR_FN_ENTRY_STR);
    cdev = kzalloc(sizeof(struct ar_cdev), GFP_KERNEL);
    if (cdev) {
        kobject_init(&cdev->kobj, &ktype_char_dev_dynamic);
        AR_DBG(DM_NET_DEV, DL_MSG, "alloc ar_cdev\n");
    }
    return cdev;
}
